local notifyCache = {0, {}}

local function getNotify()
    local num = notifyCache[1]

    if num == 0 then
        return { tb = false, key = false, old = false,
                 new = false, changed = false, userdata = false }
    else
        local temp = notifyCache[2][num]
        notifyCache[2][num] = nil
        notifyCache[1] = num - 1
        return temp
    end
end

local function retNotify(cache)
    local num = notifyCache[1] + 1
    notifyCache[1] = num
    notifyCache[2][num] = cache
end

--- 通知注册者
---@param mt table metatable
---@param key string
---@param evt table 旧值
local notify = function(mt, key, evt)
    local binds = mt.__binds[key]

    if not binds then
        return
    end

    for k, v in pairs(binds) do
        -- 如果不是强制回调，过滤掉无变化的回调
        local notifiable = v or evt.changed

        if notifiable then
            k(evt)
        end
    end
    return evt
end

---数值变化
---@param meta table 元表
---@param key string 哪个Key
---@param evt table 
---@param isBubble boolean 是否是冒泡阶段
local function valueChanged(meta, key, evt, isBubble)
    notify(meta, key, evt)

    if not isBubble and key ~= "*" then
        valueChanged(meta, "*", evt, true)
        return
    end

    if not meta.__bubble then
        ---当前Table关闭冒泡
        return
    end

    ---处于冒泡阶段
    local parent = meta.__parent
    if parent then
        valueChanged(parent, "*", evt, true)
    end
end

local function checkValueChange(tb, mt, key, old, new, isChange)
    if mt.__notify then
        local temp = getNotify()
        temp.tb = tb
        temp.key = key
        temp.old = old
        temp.new = new

        temp.changed = isChange or old ~= new
        valueChanged(mt, key, temp, false)
        retNotify(temp)
    end
end

local function setBubble(meta, bubble)
    for key, value in pairs(meta.__datas) do
        if type(value) == "table" and value.__isBind then
            setBubble(getmetatable(value), bubble)
        end
    end
    meta.__bubble = bubble
end

local function setNotify(meta, _notify)
    for key, value in pairs(meta.__datas) do
        if type(value) == "table" and value.__isBind then
            setNotify(getmetatable(value), _notify)
        end
    end
    meta.__notify = _notify
end

local bindFunc = {
    ---SetBubble:设置冒泡,默认是不启用冒泡
    SetBubble = function(self, value)
        local meta = getmetatable(self)
        setBubble(meta, value)
    end,

    ---SetNotify:设置通知,默认会通知
    SetNotify = function(self, _notify)
        local meta = getmetatable(self)
        setNotify(meta, _notify)
    end,

    ---On:订阅
    On = function(self, key, luaAction, notifyAnyway)
        if notifyAnyway == nil then
            notifyAnyway = false
        end

        local __binds = self.__binds
        local binds = __binds[key]

        if not binds then
            binds = {}
            __binds[key] = binds
        else
            if binds[luaAction] then
                error("绑定相同的方法", 2)
                return
            end
        end

        binds[luaAction] = notifyAnyway
        local temp = getNotify()

        temp.tb = self
        temp.key = key
        temp.old = nil
        temp.new = key == "*" and self or self[key]

        temp.changed = true
        luaAction(temp)
        retNotify(temp)
    end,

    ---Off:取消订阅
    Off = function(self, key, luaAction)
        local binds = self.__binds[key]
        if binds then
            binds[luaAction] = nil
        end
    end,

    ---OnAll:订阅全部
    OnAll = function(self, luaAction, notifyAnyway)
        self:On("*", luaAction, notifyAnyway)
    end,

    ---OffAll:取消订阅全部
    OffAll = function(self, luaAction)
        self:Off("*", luaAction)
    end,

    ---Publish:发布
    Publish = function(self, key)
        local value = key and self[key] or self
        key = key or "*"
        checkValueChange(self, getmetatable(self), key, value, value, true)
    end,

    ---Reset:重置数据表
    Reset = function(self)
        local parentMeta = getmetatable(self)
        parentMeta.__notify = false
        self:Clear()
        parentMeta.__notify = true
    end,

    ---Add:增加一个绑定数据（仅限数组生效）
    Add = function(self, key, child)
        if not child then
            child = child or key
            key = #self + 1
        end

        local parentMeta = getmetatable(self)
        if type(child) == "table" and child.__isBind then
            local childMeta = getmetatable(child)
            childMeta.__parent = parentMeta

            childMeta.__parentKey = key
            setBubble(childMeta, parentMeta.__bubble)
        end
        self[key] = child
    end,

    ---AddRange:插入一个数组
    AddRange = function(self, array)
        local datas = self.__datas
        for key, value in pairs(array) do
            table.insert(datas, value)
        end
        self:Publish()
    end,

    ---Indexof:查找数据在数组中的索引
    Indexof = function(self, child)
        local datas = self.__datas
        for i, v in pairs(datas) do
            if datas[i] == child then
                return i
            end
        end
        return -1
    end,

    ---Count:获取数组中元素的长度
    Count = function(self)
        local num = 0
        for i, v in pairs(self.__datas) do
            num = num + 1
        end
        return num
    end,

    ---Remove:移除一个绑定数据（仅限数组生效）
    Remove = function(self, pos)
        local meta = getmetatable(self)
        local old = table.remove(meta.__datas, pos)
        checkValueChange(self, meta, pos, old, nil, true)
    end,

    ---Remove:移除一个绑定数据（仅限数组生效）
    RemoveByValue = function(self, child)
        local meta = getmetatable(self)
        local datas = meta.__datas

        for i = #datas, 1, -1 do
            if datas[i] == child then
                table.remove(datas, i)
            end
        end
        checkValueChange(self, meta, "*", self, self, true)
    end,

    ---Clear:清除全部数据
    Clear = function(self)
        local meta = getmetatable(self)
        table.clear(meta.__datas)
        checkValueChange(self, meta, "*", self, self, true)
    end,

    ---Sort:排序（仅限数组生效）
    Sort = function(self, func)
        local meta = getmetatable(self)
        table.sort(meta.__datas, func)
        checkValueChange(self, meta, "*", self, self, true)
    end
}

---newBind:绑定类初始化
---@param raw_tb table @原始Table
---@param parentkey string 所在父table中的键
---@param parent table @元表
local function newBind(raw_tb, parentkey, parent)
    local temp = {}
    local datas = {}
    local doList = {}

    local mt
    mt = {
        __parent = parent,  --父亲  meta数据
        __parentKey = parentkey,  --父节点的Key
        __doList = doList,  --当前正在执行的触发
        __datas = datas,  --所有数据
        __binds = {},  --绑定的table

        __notify = true,  --是否通知，默认是通知
        __bubble = false,  --是否冒泡
        __isBind = true,  --是否是绑定

        __ipairs = function(tb)
            return ipairs(datas)
        end,
        __pairs = function(tb)
            return pairs(datas)
        end,

        __index = function(tb, key)
            local value = datas[key]
            if nil == value then
                value = mt[key]

                if nil == value then
                    value = bindFunc[key]
                end
            end
            return value
        end,
        __newindex = function(tb, key, value)
            local oldValue = datas[key]
            datas[key] = value

            if not doList[key] then
                doList[key] = true
                checkValueChange(tb, mt, key, oldValue, value)
                doList[key] = nil
            end
        end,
    }
    setmetatable(temp, mt)

    for k, v in pairs(raw_tb) do
        if type(v) == "table" then
            local child = v

            if v.__isBind then
                local childMeta = getmetatable(v)
                childMeta.__parent = mt
                childMeta.__parentKey = k
            else
                --如果当前的Key不是关键字，且是表格，则递归绑定
                child = newBind(v, k, mt)
            end

            mt.__datas[k] = child
        else
            mt.__datas[k] = v
        end
    end
    return temp
end

---绑定类基类
function BindClass(t)
    if type(t) ~= "table" then
        error("数据库只能接受Table类型", 2)
    end

    local meta = { __call = newBind }
    setmetatable(t, meta)
    return t
end

---空的绑定类型
---@class EmptyBind:BindClass
EmptyBind = BindClass {}

---@class BindClass
---@field SetBubble fun(self:BindClass,value:boolean) @设置冒泡,默认不启用
---@field SetNotify fun(self:BindClass,value:boolean) @设置通知,默认是开启
---@field On fun(self:BindClass,key:string,luaAction:LuaAction,notifyAnyway:boolean) @监听
---@field Off fun(self:BindClass,key:string,luaAction:LuaAction) @取消监听
---@field OnAll fun(self:BindClass,luaAction:LuaAction,notifyAnyway:boolean) @监听当前表
---@field OffAll fun(self:BindClass,luaAction:LuaAction) @取消监听当前表
---@field Add fun(self:BindClass,key:any,child:BindClass) @增加一个数据会产生父子级关系
---@field AddRange fun(self:BindClass,array:table<table>) @增加一组数据（仅限数组生效）
---@field Indexof fun(self:BindClass,child:any) @查找一个数据在数组中的索引下标
---@field Count fun(self:BindClass) @获取数组中元素的长度
---@field Remove fun(self:BindClass,pos:any) @根据下标移除一个数据（仅限数组生效）
---@field RemoveByValue fun(self:BindClass,child:any) @根据值移除一个数据（仅限数组生效）
---@field Clear fun(self:BindClass) @使用该函数可以清除数据,但会触发回调+
---@field Reset fun(self:BindClass) @使用该函数可以重置数据,但不会触发回调+
---@field Publish fun(self:BindClass,key:any) @发布通知
---@field Sort fun(self:BindClass,func:LuaAction) @排序,在排序结束会触发回调（仅限数组生效）